//
//  LcReflection.m
//  18Birdies
//
//  Created by Lc on 15/3/24.
//  Copyright (c) 2015年 Lc. All rights reserved.
//

#import <objc/runtime.h>

#import "LcFoundationException.h"
#import "LcPreconditions.h"
#import "LcReflection.h"

@interface LcDefaultObject : NSObject<NSObject>
@end

@implementation LcDefaultObject
@end

@implementation LcReflection

static NSMutableDictionary* propertiesOfClazzes;
static NSSet* defaultPropertiesFromNSObject;

+ (void)initialize {
  if (self == [LcReflection self]) {
    propertiesOfClazzes = [NSMutableDictionary dictionary];

    NSMutableSet* set = [NSMutableSet set];
    unsigned int outCount = 0;
    objc_property_t* objcProperties =
        class_copyPropertyList(LcDefaultObject.class, &outCount);
    for (int i = 0; i < outCount; ++i) {
      objc_property_t property = objcProperties[i];
      const char* objcPropertyName = property_getName(property);
      [set addObject:[NSString stringWithUTF8String:objcPropertyName]];
    }
    if (objcProperties != NULL) {
      free(objcProperties);
    }
    defaultPropertiesFromNSObject = set;
  }
}

+ (NSDictionary*)propertiesOfClass:(Class)clazz {
  LC_CHECK_NOT_NULL(clazz);
  NSString* originalClazzName = NSStringFromClass(clazz);
  NSMutableDictionary* properties = nil;
  @synchronized(self) {
    properties = propertiesOfClazzes[originalClazzName];
    if (properties == nil) {
      properties = [NSMutableDictionary dictionary];
      while (clazz != NSObject.class) {
        [self declaredPropertiesOfClass:clazz toDictionary:properties];
        clazz = clazz.superclass;
      }
      propertiesOfClazzes[originalClazzName] = properties;
    }
  }
  return properties;
}

+ (void)declaredPropertiesOfClass:(Class)clazz
                     toDictionary:(NSMutableDictionary*)properties {
  LC_CHECK_NOT_NULL(clazz);
  LC_CHECK_NOT_NULL(properties);
  unsigned int outCount = 0;
  objc_property_t* objcProperties = class_copyPropertyList(clazz, &outCount);
  for (int i = 0; i < outCount; ++i) {
    objc_property_t property = objcProperties[i];
    const char* objcPropertyName = property_getName(property);
    NSString* propertyName = [NSString stringWithUTF8String:objcPropertyName];
    if ([defaultPropertiesFromNSObject containsObject:propertyName]) {
      continue;
    }
    char* objcPropertyType = property_copyAttributeValue(property, "T");
    NSString* propertyType = [self parsePropertyType:objcPropertyType
                                             ofClass:clazz
                                     andPropertyName:propertyName];
    if (objcPropertyType != NULL) {
      free(objcPropertyType);
    }
    if (propertyName != nil && propertyType != nil) {
      if (NSClassFromString(propertyType) == NULL &&
          NSProtocolFromString(propertyType) != NULL) {
        propertyType = [NSString stringWithFormat:@"<%@>", propertyType];
      }
      properties[propertyName] = propertyType;
    }
  }
  if (objcProperties != NULL) {
    free(objcProperties);
  }
}

+ (NSString*)parsePropertyType:(const char*)objcPropertyType
                       ofClass:(Class)clazz
               andPropertyName:(NSString*)propertyName {
  LC_CHECK_NOT_NULL(clazz);
  if (objcPropertyType == NULL) {
    @throw [LcFoundationException
        exceptionWithReason:@"reflection error, objcPropertyType is null"];
  }

  if (*objcPropertyType == '@') {
    int k = 0;
    char parsedPropertyType[128];
    objcPropertyType += 2;
    for (; *objcPropertyType != '\"';) {
      parsedPropertyType[k++] = *objcPropertyType;
      ++objcPropertyType;
    }
    parsedPropertyType[k] = '\0';
    return [NSString stringWithUTF8String:parsedPropertyType];
  } else if (*objcPropertyType == 'i') {
    return @"int";
  } else if (*objcPropertyType == 's') {
    return @"short";
  } else if (*objcPropertyType == 'l') {
    return @"long";
  } else if (*objcPropertyType == 'q') {
    return @"long long";
  } else if (*objcPropertyType == 'f') {
    return @"float";
  } else if (*objcPropertyType == 'd') {
    return @"double";
  } else if (*objcPropertyType == 'c' || *objcPropertyType == 'B') {
    return @"BOOL";
  }

  // not supported property types, such as C structures, unsigned primitive
  // types or primitive arrays, thery are not used in most cases
  @throw [LcFoundationException
      exceptionWithReason:
          [NSString stringWithFormat:@"not supported property type found, "
                                     @"class=%@, propertyName=%@, "
                                     @"objcPropertyType=%s",
                                     NSStringFromClass(clazz), propertyName,
                                     objcPropertyType]];
}

+ (BOOL)isPrimitiveType:(NSString*)type {
  if ([type isEqualToString:@"int"] || [type isEqualToString:@"short"] ||
      [type isEqualToString:@"long"] || [type isEqualToString:@"long long"] ||
      [type isEqualToString:@"float"] || [type isEqualToString:@"double"] ||
      [type isEqualToString:@"BOOL"]) {
    return YES;
  } else {
    return NO;
  }
}

+ (BOOL)isProtocol:(NSString*)type {
  if ([type hasPrefix:@"<"] && [type hasSuffix:@">"]) {
    return YES;
  } else {
    return NO;
  }
}

+ (BOOL)isCollectionType:(NSString*)type {
  return [self isType:type KindOfClass:NSArray.class] ||
         [self isType:type KindOfClass:NSSet.class] ||
         [self isType:type KindOfClass:NSOrderedSet.class] ||
         [self isType:type KindOfClass:NSDictionary.class];
}

+ (BOOL)isType:(NSString*)type KindOfClass:(Class)clazz {
  LC_CHECK_NOT_NULL(clazz);
  Class propertyClazz = NSClassFromString(type);
  if (propertyClazz == NULL) {
    // non ObjectiveC class
    return NO;
  }
  while (propertyClazz != NSObject.class) {
    if (propertyClazz == clazz) {
      return YES;
    }
    propertyClazz = propertyClazz.superclass;
  }
  if (propertyClazz == clazz) {
    return YES;
  }
  return NO;
}

@end
